/**
 * 雪花 ID
 *
 * 一般来说，雪花 ID 不会由前端生成。
 *
 * 但是不排除这个可能，却是会产生有一些很奇怪的业务。
 *
 * 设计背景：后台使用到了自增 ID，在提交之前，不会产生数据 ID。
 * 但是有些业务，必须提前知道这个 ID，这时候可以通过当前函数生成 ID，将两份数据关联在一起。
 */
class Snowflake53 {
    constructor(options = {}) {
        this.epoch = options.epoch || 1609459200000; // 2021-01-01
        this.machineId = options.machineId || 0;     // 0-1023
        this.sequence = 0;
        this.lastTimestamp = 0;

        // 调整位数分配（总和小于等于 53）
        this.timestampBits = 41; // 时间戳占 41 位（最大支持 69 年）
        this.machineIdBits = 2; // 机器 ID 占 2 位 (因为没集群的概念，实际可以不需要)
        this.sequenceBits = 8; // 序列号占 10 位

        this.maxMachineId = (1 << this.machineIdBits) - 1;
        this.maxSequence = (1 << this.sequenceBits) - 1;


        if (this.machineId > this.maxMachineId || this.machineId < 0) {
            throw new Error(`Machine ID must be between 0 and ${this.maxMachineId}`);
        }

        // 提前算好机器码
        this.machine_code = BigInt(this.machineId << this.sequenceBits);

        // 提前计算好位移
        this.shift = BigInt(this.machineIdBits + this.sequenceBits);
    }

    currentTimestamp() {
        return Date.now();
    }

    nextId() {
        let timestamp = this.currentTimestamp();

        if (timestamp < this.lastTimestamp) {
            throw new Error('Clock moved backwards.');
        }

        if (timestamp === this.lastTimestamp) {
            this.sequence = (this.sequence + 1) & this.maxSequence;
            if (this.sequence === 0) {
                while (timestamp <= this.lastTimestamp) {
                    timestamp = this.currentTimestamp();
                }
            }
        } else {
            this.sequence = 0;
        }

        this.lastTimestamp = timestamp;

        // 组合 ID（使用 Number 类型）
        const id = (BigInt(timestamp - this.epoch) << this.shift) |
            this.machine_code | BigInt(this.sequence);

        // 确保为正整数
        if (id < 0 || id > Number.MAX_SAFE_INTEGER) {
            throw new Error('Generated ID exceeds safe integer range.');
        }

        return id;
    }
}

// 前端没什么并发毛病，直接使用单例就好了
const snowflake = new Snowflake53();

export default snowflake;